type Action = () => void;

export const appendAll = <T extends HTMLElement>(
    parent: T,
    ...childs: UIElement<any>[][]
) => {
    for (const cc of childs) {
        for (const c of cc)
            parent.appendChild(c.root);
        parent.appendChild(document.createElement('br'));
    }
    return parent;
}

export const removeElement = (node: HTMLElement) => {
    const parent = node.parentElement;
    if (parent !== null)
        parent.removeChild(node);
}

export abstract class UIElement<T extends HTMLElement = HTMLDivElement>
{
    root: T;
    constructor(element: T) { this.root = element; }
}

export class UIButton
    extends UIElement<HTMLButtonElement>
{
    constructor(text: string, onClick: Action) {
        const button = document.createElement('button');
        button.innerHTML = text;
        button.onclick = onClick;

        super(button);
    }
}

interface IInput<T> { readonly value: T; }

export type IUIInput<
    T,
    U extends HTMLElement = HTMLElement,
> = IInput<T> & UIElement<U>;

export class UITextInput
    extends UIElement
    implements IInput<string>
{
    private input: HTMLTextAreaElement;

    constructor(name: string) {
        const div = document.createElement('div');
        const text = document.createTextNode(name + ' : ');
        const input = document.createElement('textarea');
        div.appendChild(text);
        div.appendChild(document.createElement('br'));
        div.appendChild(input);

        super(div);
        this.input = input;
    }

    get value() { return this.input.value; }
}

export class UICheckBox<T>
    extends UIElement
    implements IInput<T>
{
    private static nextId: number;
    private static generateName() {
        return `check-box-group-${UICheckBox.nextId++}`;
    }

    private values: Array<T>;

    constructor(options: Record<string, T>, name?: string) {
        const div = document.createElement('div');
        super(div);

        const keys = Object.keys(options);
        if (keys.length === 0)
            throw new Error('options length = 0');
        this.values = Object.values(options);

        if (name === undefined)
            name = UICheckBox.generateName();

        keys.forEach((key, index) => {
            const input = document.createElement('input');
            input.type = 'radio';
            input.name = name!;
            input.value = index.toString();
            if (index === 0)
                input.checked = true;
            div.appendChild(input);
            div.appendChild(document.createTextNode(key));
        });
    }

    get value(): T {
        const key = this.root.querySelector<HTMLInputElement>(':checked')!.value;
        return this.values[parseInt(key)];
    }
}

export const mapUIInput = <T, U, HT extends HTMLElement>(
    it: UIElement<HT> & IInput<T>,
    f: (t: T) => U,
): UIElement<HT> & IInput<U> => ({
    root: it.root,
    get value() {
        return f(it.value);
    },
});
